前偏簡單的介紹了Debug Module所提供的功能以及Target狀態控制...等等,
今天就要來探討一下Debug Module中的核心功能"Abstract Commands"和"Program Buffer"
  
  
  
Program Buffer存在的主要目的就是可以在Target上,讓Debugger可以撰寫一些小程式,
執行任意的指令!
另外,根據Spec.中的定義,Program Buffer最後一道指令必須是"ebreak"或是"c.ebreak"之外,其餘的指令皆可任意在Program Buffer上執行,不分16-bits或是32-bits的指令皆可!
以下舉幾個簡單的例子和實作分別說明Program Buffer的用處!
先看一個簡單的Read GPR $x1的例子(64-bits的平台)
sd      $x1, 0x98(zero)
fence
ebreak
簡單的三行指令就可以把$x1搬到0x98的位置存放起來!
然後只要從那邊讀出資料,就可以知道$x1的內容!
實作方式,請參考以下內容(src/target/riscv/riscv-013.c的古早版本):
static int register_read_direct(struct target *target, uint64_t *value, uint32_t number)
{
        struct riscv_program program;
        riscv_program_init(&program, target);
        riscv_addr_t output = riscv_program_alloc_d(&program);
        riscv_program_write_ram(&program, output + 4, 0);
        riscv_program_write_ram(&program, output, 0);
        assert(GDB_REGNO_XPR0 == 0);
        if (number <= GDB_REGNO_XPR31) {
            riscv_program_sx(&program, number, output);     ///譯註: 裡面再去區分需要sd(64-bits) / sw(32-bits)
        } else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
            riscv_program_fsx(&program, number, output);
        } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
            LOG_DEBUG("reading CSR index=0x%03x", number - GDB_REGNO_CSR0);
            enum gdb_regno temp = riscv_program_gettemp(&program);
            riscv_program_csrr(&program, temp, number);
            riscv_program_sx(&program, temp, output);
        } else {
            LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
            abort();
        }
        int exec_out = riscv_program_exec(&program, target);    ///譯註:裡面會補上fence和ebreak,並執行Program Buffer!
        if (exec_out != ERROR_OK) {
            riscv013_clear_abstract_error(target);
            return ERROR_FAIL;
        }
        *value = 0;
        *value |= ((uint64_t)(riscv_program_read_ram(&program, output + 4))) << 32;
        *value |= riscv_program_read_ram(&program, output);
        LOG_DEBUG("[%d] reg[0x%x] = 0x%" PRIx64, riscv_current_hartid(target),
                number, *value);
        return ERROR_OK;
}
  
  
同上一節的Read Registers,我們考慮以下範例:
ld      x1, 0x98(zero)
fence
ebreak
簡單的三行指令就可以把0x98中的資料搬到$x1的位置存放起來!
實作方式,請參考以下內容(src/target/riscv/riscv-013.c的古早版本):
static int register_write_direct(struct target *target, unsigned number,
        uint64_t value)
{
    LOG_DEBUG("[%d] reg[0x%x] <- 0x%" PRIx64, riscv_current_hartid(target),
            number, value);
    struct riscv_program program;
    riscv_program_init(&program, target);
    riscv_addr_t input = riscv_program_alloc_d(&program);
    riscv_program_write_ram(&program, input + 4, value >> 32);
    riscv_program_write_ram(&program, input, value);
    assert(GDB_REGNO_XPR0 == 0);
    if (number <= GDB_REGNO_XPR31) {
        riscv_program_lx(&program, number, input);     ///譯註: 裡面再去區分需要ld(64-bits) / lw(32-bits)
    } else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
        riscv_program_flx(&program, number, input);
    } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
        enum gdb_regno temp = riscv_program_gettemp(&program);
        riscv_program_lx(&program, temp, input);
        riscv_program_csrw(&program, temp, number);
    } else {
        LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
        abort();
    }
    int exec_out = riscv_program_exec(&program, target);
    if (exec_out != ERROR_OK) {
        riscv013_clear_abstract_error(target);
        return ERROR_FAIL;
    }
    return ERROR_OK;
}
基本上流程同Read Registers
  
  
先看一個簡單的Read GPR $x1的例子(64-bits的平台)
addi    s0,s0,4
lw      s1,0(s0)
sw      s1,0x9c(zero)
fence   unknown,unknown
ebreak
假設$s0中負責存目標的Address,$s1中負責存放Data!
以上程式的流程如下:
每執行一次這個小程式,就可以讀出一筆Data!
假如需要連續讀取複數個Memory,是不是很方便啊!?
以下是實作的方式,由於內容有點複雜,有稍微修改一下,請參考以下內容(src/target/riscv/riscv-013.c的古早版本):
static int read_memory(struct target *target, target_addr_t address,
        uint32_t size, uint32_t count, uint8_t *buffer)
{
    ....省略
    struct riscv_program program;
    riscv_program_init(&program, target);
    riscv_addr_t r_data = riscv_program_alloc_w(&program);
    riscv_addr_t r_addr = riscv_program_alloc_x(&program);
    riscv_program_fence(&program);
    riscv_program_lx(&program, GDB_REGNO_S0, r_addr);
    switch (size) {
        case 1:
            riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        case 2:
            riscv_program_lhr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        case 4:
            riscv_program_lwr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        default:
            LOG_ERROR("Unsupported size: %d", size);
            return ERROR_FAIL;
    }
    riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
    riscv_program_sw(&program, GDB_REGNO_S1, r_data);
    riscv_program_sx(&program, GDB_REGNO_S0, r_addr);
    /* The first round through the program's execution we use the regular
     * program execution mechanism. */
    switch (riscv_xlen(target)) {
    case 64:
        riscv_program_write_ram(&program, r_addr + 4, ((riscv_addr_t) address) >> 32);
    case 32:
        riscv_program_write_ram(&program, r_addr, (riscv_addr_t) address);
        break;
    default:
        LOG_ERROR("unknown XLEN %d", riscv_xlen(target));
        return ERROR_FAIL;
    }
    if (riscv_program_exec(&program, target) != ERROR_OK) {
        ....省略錯誤處理的部分
    }
    int d_data = (r_data - riscv_debug_buffer_addr(target)) / 4;
    int d_addr = (r_addr - riscv_debug_buffer_addr(target)) / 4;
    riscv_addr_t cur_addr = riscv_read_debug_buffer_x(target, d_addr);
    riscv_addr_t fin_addr = address + (count * size);
    while (cur_addr < fin_addr) {
        ...基本上就是重複以上流程,直到讀取完畢為止!
    
    }
    ....省略
中間過程落落長的,有興趣的讀者可以自行研究!
  
  
基本上同上一節的Read Memory,我們考慮以下範例:
lw      s1,0x9c(zero) 
sw      s1,0(s0)
addi    s0,s0,4
fence
ebreak
一樣假設$s0中負責存目標的Address,$s1中負責存放Data!
以上程式的流程如下:
以下是實作的方式,由於內容有點複雜,一樣只節錄部分內容,
請參考以下內容(src/target/riscv/riscv-013.c的古早版本):
static int write_memory(struct target *target, target_addr_t address,
        uint32_t size, uint32_t count, const uint8_t *buffer)
{
    ....省略
    struct riscv_program program;
    riscv_program_init(&program, target);
    riscv_addr_t r_data = riscv_program_alloc_w(&program);
    riscv_addr_t r_addr = riscv_program_alloc_x(&program);
    riscv_program_fence(&program);
    riscv_program_lx(&program, GDB_REGNO_S0, r_addr);
    riscv_program_lw(&program, GDB_REGNO_S1, r_data);
    switch (size) {
        case 1:
            riscv_program_sbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        case 2:
            riscv_program_shr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        case 4:
            riscv_program_swr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        default:
            LOG_ERROR("Unsupported size: %d", size);
            return ERROR_FAIL;
    }
    riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
    riscv_program_sx(&program, GDB_REGNO_S0, r_addr);
    ....省略
    if (riscv_program_exec(&program, target) != ERROR_OK) {
        ....省略錯誤處理的部分
    }
    /* The rest of this program is designed to be fast so it reads various
     * DMI registers directly. */
    int d_data = (r_data - riscv_debug_buffer_addr(target)) / 4;
    int d_addr = (r_addr - riscv_debug_buffer_addr(target)) / 4;
    riscv_addr_t cur_addr = 0xbadbeef;
    riscv_addr_t fin_addr = address + (count * size);
    LOG_DEBUG("writing until final address 0x%016" PRIx64, fin_addr);
    while ((cur_addr = riscv_read_debug_buffer_x(target, d_addr)) < fin_addr) {
        
        ...基本上就是重複以上流程,直到讀取完畢為止!
    }
    ....省略
}
  
  
  
終於講完Program Buffer基本的操作!
以整體架構上來看,Program Buffer基本上就是萬能的,又可以在上面執行任意指令,
那又為啥需要提供Abstract Commands的功能!?
換個角度想一下,假如今天硬體能夠對特定功能把它抽象化,讓它們從一道道的指令,
變成一個預先定義好的Register,然後透過對這個Register讀寫的操作來執行動作,
是不是就能夠加速Debugger得到資料!!!  (有沒有很厲害的設計!
以上節的例子來說,就是把Access(包含Read/Write) Register,
直接變成一道指令的話,那我們是不是可以省掉大量Program Buffer操作的時間!
  
  
首先先來定義一下這個指令的格式:

---引用自RISC-V External Debug Support 0.13
這邊講解一下每個Field的意義:
這個Abstract Command的主要功能是用在Access(包含Read/Write) Register、Programm Buffer上
首先,一樣是先來看一下這個指令的格式:

---引用自RISC-V External Debug Support 0.13

---引用自RISC-V External Debug Support 0.13
因此,從上面的組合中,可以將這個Abstract Command提供的功能拆成以下幾個:
再過來就是實作的部分啦,請參考以下內容(src/target/riscv/riscv-013.c)
static int register_read_abstract(struct target *target, uint64_t *value,
        uint32_t number, unsigned size)
{
    RISCV013_INFO(info);
    /// 譯註: 目前這版只支援Access基本的CSRs
    if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
            !info->abstract_read_fpr_supported)
        return ERROR_FAIL;
    if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095 &&
            !info->abstract_read_csr_supported)
        return ERROR_FAIL;
    uint32_t command = access_register_command(number, size,
            AC_ACCESS_REGISTER_TRANSFER);
    int result = execute_abstract_command(target, command);
    if (result != ERROR_OK) {
        if (info->cmderr == CMDERR_NOT_SUPPORTED) {
            if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
                info->abstract_read_fpr_supported = false;
                LOG_INFO("Disabling abstract command reads from FPRs.");
            } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
                info->abstract_read_csr_supported = false;
                LOG_INFO("Disabling abstract command reads from CSRs.");
            }
        }
        return result;
    }
    if (value)
        *value = read_abstract_arg(target, 0);
    return ERROR_OK;
}
Register Read的部分也類似,請參考以下內容(src/target/riscv/riscv-013.c)
static int register_write_abstract(struct target *target, uint32_t number,
        uint64_t value, unsigned size)
{
    RISCV013_INFO(info);
    if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
            !info->abstract_write_fpr_supported)
        return ERROR_FAIL;
    if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095 &&
            !info->abstract_write_csr_supported)
        return ERROR_FAIL;
    uint32_t command = access_register_command(number, size,
            AC_ACCESS_REGISTER_TRANSFER |
            AC_ACCESS_REGISTER_WRITE);
    if (write_abstract_arg(target, 0, value) != ERROR_OK) {
        return ERROR_FAIL;
    }
    int result = execute_abstract_command(target, command);
    if (result != ERROR_OK) {
        if (info->cmderr == CMDERR_NOT_SUPPORTED) {
            if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
                info->abstract_write_fpr_supported = false;
                LOG_INFO("Disabling abstract command writes to FPRs.");
            } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
                info->abstract_write_csr_supported = false;
                LOG_INFO("Disabling abstract command writes to CSRs.");
            }
        }
        return result;
    }
    return ERROR_OK;
}
  
  
首先,一樣是先來看一下這個指令的格式:

---引用自RISC-V External Debug Support 0.13
沒什麼好說的,cmdtype這邊固定為1!
主要提供的動作如下:
當Debugger無法在Hart的狀態是Runing下讀取資料時,可以利用Quick Access短暫的暫停Hart,然後執行預先寫好的Program Buffer取得所需的資料,在自動去Resume,避免長時間的將Hart停下!
不過由於目前這版還沒看到有使用到,這邊姑且只是翻譯一下Spec.的內容!
  
  
  
以上就是我覺得Debug Module精華的部分!
另外明天的主題將會深入介紹Debug Module中常用到的Registers,
應該比較繁瑣一些~~~XD